//
//  Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//

#import "TSGroupThread.h"
#import "TSAttachmentStream.h"
#import <SignalCoreKit/NSData+OWS.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <SignalServiceKit/TSAccountManager.h>

NS_ASSUME_NONNULL_BEGIN

NSString *const TSGroupThreadAvatarChangedNotification = @"TSGroupThreadAvatarChangedNotification";
NSString *const TSGroupThread_NotificationKey_UniqueId = @"TSGroupThread_NotificationKey_UniqueId";

@interface TSGroupThread ()

@property (nonatomic) TSGroupModel *groupModel;

@end

#pragma mark -

@implementation TSGroupThread

#define TSGroupThreadPrefix @"g"

// --- CODE GENERATION MARKER

// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run `sds_codegen.sh`.

// clang-format off

- (instancetype)initWithGrdbId:(int64_t)grdbId
                      uniqueId:(NSString *)uniqueId
           conversationColorName:(ConversationColorName)conversationColorName
                    creationDate:(nullable NSDate *)creationDate
                      isArchived:(BOOL)isArchived
            lastInteractionRowId:(int64_t)lastInteractionRowId
                    messageDraft:(nullable NSString *)messageDraft
                  mutedUntilDate:(nullable NSDate *)mutedUntilDate
           shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
                      groupModel:(TSGroupModel *)groupModel
{
    self = [super initWithGrdbId:grdbId
                        uniqueId:uniqueId
             conversationColorName:conversationColorName
                      creationDate:creationDate
                        isArchived:isArchived
              lastInteractionRowId:lastInteractionRowId
                      messageDraft:messageDraft
                    mutedUntilDate:mutedUntilDate
             shouldThreadBeVisible:shouldThreadBeVisible];

    if (!self) {
        return self;
    }

    _groupModel = groupModel;

    return self;
}

// clang-format on

// --- CODE GENERATION MARKER

- (MessageSenderJobQueue *)messageSenderJobQueue
{
    return SSKEnvironment.shared.messageSenderJobQueue;
}

- (instancetype)initWithGroupModelPrivate:(TSGroupModel *)groupModel
{
    OWSAssertDebug(groupModel);
    OWSAssertDebug(groupModel.groupId.length > 0);
    for (SignalServiceAddress *address in groupModel.groupMembers) {
        OWSAssertDebug(address.isValid);
    }

    NSString *uniqueIdentifier = [[self class] threadIdFromGroupId:groupModel.groupId];
    self = [super initWithUniqueId:uniqueIdentifier];
    if (!self) {
        return self;
    }

    _groupModel = groupModel;

    return self;
}

+ (nullable instancetype)fetchWithGroupId:(NSData *)groupId transaction:(SDSAnyReadTransaction *)transaction
{
    OWSAssertDebug(groupId.length > 0);

    NSString *uniqueId = [self threadIdFromGroupId:groupId];
    return [TSGroupThread anyFetchGroupThreadWithUniqueId:uniqueId transaction:transaction];
}

+ (NSString *)threadIdFromGroupId:(NSData *)groupId
{
    OWSAssertDebug(groupId.length > 0);

    return [TSGroupThreadPrefix stringByAppendingString:[groupId base64EncodedString]];
}

+ (NSData *)groupIdFromThreadId:(NSString *)threadId
{
    OWSAssertDebug(threadId.length > 0);

    return [NSData dataFromBase64String:[threadId substringWithRange:NSMakeRange(1, threadId.length - 1)]];
}

- (NSArray<SignalServiceAddress *> *)recipientAddresses
{
    NSMutableArray<SignalServiceAddress *> *groupMembers = [self.groupModel.groupMembers mutableCopy];
    if (groupMembers == nil) {
        return @[];
    }

    [groupMembers removeObject:TSAccountManager.localAddress];

    return [groupMembers copy];
}

// @returns all threads to which the recipient is a member.
//
// @note If this becomes a hotspot we can extract into a YapDB View.
// As is, the number of groups should be small (dozens, *maybe* hundreds), and we only enumerate them upon SN changes.
+ (NSArray<TSGroupThread *> *)groupThreadsWithAddress:(SignalServiceAddress *)address
                                          transaction:(SDSAnyReadTransaction *)transaction
{
    OWSAssertDebug(address.isValid);
    OWSAssertDebug(transaction);

    NSMutableArray<TSGroupThread *> *groupThreads = [NSMutableArray new];

    [TSThread anyEnumerateWithTransaction:transaction
                                  batched:YES
                                    block:^(TSThread *thread, BOOL *stop) {
                                        if ([thread isKindOfClass:[TSGroupThread class]]) {
                                            TSGroupThread *groupThread = (TSGroupThread *)thread;
                                            if ([groupThread.groupModel.groupMembers containsObject:address]) {
                                                [groupThreads addObject:groupThread];
                                            }
                                        }
                                    }];

    return [groupThreads copy];
}

- (BOOL)isGroupThread
{
    return true;
}

- (BOOL)isGroupV1Thread
{
    return self.groupModel.groupsVersion == GroupsVersionV1;
}

- (BOOL)isGroupV2Thread
{
    return self.groupModel.groupsVersion == GroupsVersionV2;
}

- (BOOL)isLocalUserInGroup
{
    SignalServiceAddress *_Nullable localAddress = TSAccountManager.localAddress;
    if (localAddress == nil) {
        return NO;
    }

    return [self.groupModel.groupMembers containsObject:localAddress];
}

- (NSString *)groupNameOrDefault
{
    return self.groupModel.groupNameOrDefault;
}

+ (NSString *)defaultGroupName
{
    return NSLocalizedString(@"NEW_GROUP_DEFAULT_TITLE", @"");
}

#pragma mark - Avatar

- (void)updateAvatarWithAttachmentStream:(TSAttachmentStream *)attachmentStream
{
    [self.databaseStorage writeWithBlock:^(SDSAnyWriteTransaction *transaction) {
        [self updateAvatarWithAttachmentStream:attachmentStream transaction:transaction];
    }];
}

- (void)updateAvatarWithAttachmentStream:(TSAttachmentStream *)attachmentStream
                             transaction:(SDSAnyWriteTransaction *)transaction
{
    OWSAssertDebug(attachmentStream);
    OWSAssertDebug(transaction);

    [self anyUpdateGroupThreadWithTransaction:transaction
                                        block:^(TSGroupThread *thread) {
                                            NSData *_Nullable attachmentData =
                                                [NSData dataWithContentsOfFile:attachmentStream.originalFilePath];
                                            if (attachmentData.length < 1) {
                                                return;
                                            }
                                            if (thread.groupModel.groupAvatarData.length > 0 &&
                                                [thread.groupModel.groupAvatarData isEqualToData:attachmentData]) {
                                                // Avatar did not change.
                                                return;
                                            }
                                            UIImage *_Nullable avatarImage = [attachmentStream thumbnailImageSmallSync];
                                            [thread.groupModel setGroupAvatarDataWithImage:avatarImage];
                                        }];

    [transaction addAsyncCompletion:^{
        [self fireAvatarChangedNotification];
    }];

    // Avatars are stored directly in the database, so there's no need
    // to keep the attachment around after assigning the image.
    [attachmentStream anyRemoveWithTransaction:transaction];
}

- (void)updateWithGroupModel:(TSGroupModel *)groupModel transaction:(SDSAnyWriteTransaction *)transaction
{
    OWSAssertDebug(groupModel);
    OWSAssertDebug(transaction);
    OWSAssertDebug(groupModel.groupsVersion == self.groupModel.groupsVersion);

    BOOL didAvatarChange = ![NSObject isNullableObject:groupModel.groupAvatarData
                                               equalTo:self.groupModel.groupAvatarData];

    [self anyUpdateGroupThreadWithTransaction:transaction
                                        block:^(TSGroupThread *thread) {
                                            OWSAssertDebug(
                                                thread.groupModel.groupV2Revision <= groupModel.groupV2Revision);
                                            thread.groupModel = [groupModel copy];
                                        }];

    if (didAvatarChange) {
        [transaction addAsyncCompletion:^{
            [self fireAvatarChangedNotification];
        }];
    }
}

- (void)fireAvatarChangedNotification
{
    OWSAssertIsOnMainThread();

    NSDictionary *userInfo = @{ TSGroupThread_NotificationKey_UniqueId : self.uniqueId };

    [[NSNotificationCenter defaultCenter] postNotificationName:TSGroupThreadAvatarChangedNotification
                                                        object:self.uniqueId
                                                      userInfo:userInfo];
}

+ (ConversationColorName)defaultConversationColorNameForGroupId:(NSData *)groupId
{
    OWSAssertDebug(groupId.length > 0);

    return [self.class stableColorNameForNewConversationWithString:[self threadIdFromGroupId:groupId]];
}

- (NSSet<SignalServiceAddress *> *)allPendingAndNonPendingMembers
{
    return self.groupModel.allPendingAndNonPendingMembers;
}

@end

NS_ASSUME_NONNULL_END
